﻿using System;
using System.Globalization;
using System.Windows.Data;

namespace AppToolkit.WPF.UI.Converters
{
    /// <summary>
    /// Simplifies implementing typesafe <see cref="IValueConverter"/> implementations.
    /// </summary>
    /// <typeparam name="V">source type from which to convert</typeparam>
    /// <typeparam name="T">target type to convert to</typeparam>
    /// <typeparam name="P">type of additional parameter that is used during conversion.</typeparam>
    public abstract class BaseValueConverter<V, T, P> : IValueConverter
    {
        /// <summary>
        /// Forward conversion of value.
        /// </summary>
        /// <param name="value">The value produced by the binding source.</param>
        /// <param name="targetType">The type of the binding target property.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>
        /// A converted value. If the method returns null, the valid null value is used.
        /// </returns>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value.GetType() != typeof(V))
                throw new ArgumentException(GetType().Name + ".Convert: value type not " + typeof(V).Name);
            if (targetType != typeof(T))
                throw new ArgumentException(GetType().Name + ".Convert: target type not " + typeof(T).Name);
            return Convert((V)value, (P)parameter, culture);
        }

        /// <summary>
        /// Backward conversion of value.
        /// </summary>
        /// <param name="value">The value that is produced by the binding target.</param>
        /// <param name="targetType">The type to convert to.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>
        /// A converted value. If the method returns null, the valid null value is used.
        /// </returns>
        public object ConvertBack(object value, Type targetType,
          object parameter, CultureInfo culture)
        {
            if (value.GetType() != typeof(T))
                throw new ArgumentException(GetType().Name + ".ConvertBack: value type not " + typeof(T).Name);
            if (targetType != typeof(V))
                throw new ArgumentException(GetType().Name + ".ConvertBack: target type not " + typeof(V).Name);
            return ConvertBack((T)value, (P)parameter, culture);
        }

        /// <summary>
        /// Override this method to implement forward conversion.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="parameter">The parameter.</param>
        /// <param name="culture">The culture.</param>
        /// <returns></returns>
        protected virtual T Convert(V value, P parameter,
          CultureInfo culture)
        {
            throw new NotImplementedException(GetType().Name + "Convert not implemented");
        }

        /// <summary>
        /// Override this method to implement backward conversion.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="parameter">The parameter.</param>
        /// <param name="culture">The culture.</param>
        /// <returns></returns>
        protected virtual V ConvertBack(T value,
          P parameter, CultureInfo culture)
        {
            throw new NotImplementedException(GetType().Name + "ConvertBack not implemented");
        }
    }

    /// <summary>
    /// Simplifies implementing typesafe <see cref="IValueConverter"/> implementations.
    /// </summary>
    /// <typeparam name="V">source type from which to convert</typeparam>
    /// <typeparam name="T">target type to convert to</typeparam>
    public abstract class BaseValueConverter<V, T>
      : BaseValueConverter<V, T, object>
    {
        /// <summary>
        /// Override this method to implement forward conversion.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="culture">The culture.</param>
        /// <returns></returns>
        protected virtual T Convert(V value, CultureInfo culture)
        {
            throw new NotImplementedException(GetType().Name + "Convert not implemented");
        }

        /// <summary>
        /// Override this method to implement backward conversion.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="culture">The culture.</param>
        /// <returns></returns>
        protected virtual V ConvertBack(T value, CultureInfo culture)
        {
            throw new NotImplementedException(GetType().Name + "ConvertBack not implemented");
        }

        /// <summary>
        /// Override this method to implement forward conversion.
        /// </summary>
        /// <param name="value">source value</param>
        /// <param name="parameter"></param>
        /// <param name="culture"></param>
        /// <returns></returns>
        protected sealed override T Convert(V value, object parameter, CultureInfo culture)
        {
            if (parameter != null)
                throw new ArgumentException(GetType().Name + ".Convert: binding contains unexpected parameter");
            return Convert(value, culture);
        }

        /// <summary>
        /// Override this method to implement backward conversion.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="parameter"></param>
        /// <param name="culture"></param>
        /// <returns></returns>
        protected sealed override V ConvertBack(T value, object parameter, CultureInfo culture)
        {
            if (parameter != null)
                throw new ArgumentException(GetType().Name + ".ConvertBack: binding contains unexpected parameter");
            return ConvertBack(value, culture);
        }
    }
}